package com.jwater.core.manager.worker.assign;

import com.jwater.core.manager.common.DataKeys;
import com.jwater.core.manager.common.conf.CoreConfParse;
import com.jwater.core.manager.common.model.WorkerInfo;
import com.jwater.core.metrics.SystemMetric;
import com.jwater.launch.HomePath;
import jwater.org.apache.commons.io.FileUtils;
import jwater.org.springframework.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.util.Map;
import java.util.Random;
import java.util.Set;

/**
 * @description:
 * @author: jnner
 * @createDate: 2021/3/6
 * @version: 1.0
 */
public class AssignPort {
    protected static Logger logger = LoggerFactory.getLogger(AssignPort.class);

    public static void assign(WorkerInfo workerInfo) {
        String tcpPortName = workerInfo.getProps().getString("tcp.portName");
        String udpPortName = workerInfo.getProps().getString("udp.portName");
        if (StringUtils.isEmpty(tcpPortName) && StringUtils.isEmpty(udpPortName)) {
            return;
        }
        logger.info("tcpPortName:" + tcpPortName + ",udpPortName:" + udpPortName);
        Map<String, Set<Integer>> usedPort = SystemMetric.getUsedPort();
        Set<Integer> usedTcpPort = usedPort.get("tcp");
        Set<Integer> usedUdpPort = usedPort.get("udp");
        int[] portRange = getPortRange();
        if (!StringUtils.isEmpty(tcpPortName)) {
            for (String item : tcpPortName.split(",")) {
                Integer port = assignPort(usedTcpPort, portRange, workerInfo, item, "tcp");
                if (port == null) {
                    logger.warn("assign tcp port error:" + item);
                } else {
                    logger.info("assign tcp port " + item + "=" + port);
                    System.setProperty(item, port + "");
                    WorkerEnv.getInstance().put(item, port + "");
                }
            }
        }

        if (!StringUtils.isEmpty(udpPortName)) {
            for (String item : udpPortName.split(",")) {
                Integer port = assignPort(usedUdpPort, portRange, workerInfo, item, "udp");
                if (port == null) {
                    logger.warn("assign udp port error:" + item);
                } else {
                    logger.info("assign udp port " + item + "=" + port);
                    System.setProperty(item, port + "");
                    WorkerEnv.getInstance().put(item, port + "");
                }
            }
        }
    }

    private static int[] getPortRange() {
        CoreConfParse coreConfParse = new CoreConfParse();
        coreConfParse.parse();
        return coreConfParse.getWorkerPortRange();
    }

    private static Integer assignPort(Set<Integer> usedPorts, int[] portRange, WorkerInfo workerInfo,
                                      String portName, String type) {
        Integer lastPort = getLastPort(workerInfo, portName, type);
        if (lastPort != null && !usedPorts.contains(lastPort)) {
            return lastPort;
        }
        int count = 0;
        while (count < 10) {
            int port = new Random(System.currentTimeMillis()).nextInt(portRange[1] - portRange[0]) + portRange[0];
            if (!usedPorts.contains(port)) {
                savePort(workerInfo, portName, type, port);
                return port;
            }
            count++;
        }
        return null;
    }

    private static void savePort(WorkerInfo workerInfo, String portName, String type, int port) {
        try {
            File dir = new File(HomePath.getDataPath(), type);
            if (!dir.exists()) {
                dir.mkdirs();
            }
            String key = DataKeys.getRunWorkerKey(workerInfo.getRegionName(), workerInfo.getDeployName(),
                    workerInfo.getGroupName(), workerInfo.getWorkerName(), workerInfo.getIndex())
                    .replace(":", "#");
            File file = new File(dir, key + ":" + portName);
            FileUtils.write(file, port + "");
        } catch (Exception e) {
            logger.error(e.toString(), e);
        }
    }

    private static Integer getLastPort(WorkerInfo workerInfo, String portName, String type) {
        try {
            File dir = new File(HomePath.getDataPath(), type);
            if (!dir.exists()) {
                dir.mkdirs();
            }
            String key = DataKeys.getRunWorkerKey(workerInfo.getRegionName(), workerInfo.getDeployName(),
                    workerInfo.getGroupName(), workerInfo.getWorkerName(), workerInfo.getIndex())
                    .replace(":", "#");
            logger.info(key);
            File file = new File(dir, key + ":" + portName);
            if (file.exists()) {
                return Integer.parseInt(FileUtils.readFileToString(file));
            }
        } catch (Exception e) {
            logger.error(e.toString(), e);
        }
        return null;
    }
}
